// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// A MultiProbe does DNS resolution.  It then uses the factory to create a
// probe for each record found at that address.  It scrambles the order, and
// runs a "short" probe at all but the last address, where a full probe is
// run.  It looks at the response codes and differentiates the case where
// all the addresses return the same problem vs. the cases where they differ.
// Author: Jeff Stewart


#include <QtCore/QTimer>
#include <algorithm>

#include "probes/multiprobe.h"


namespace popdiag {

MultiProbe::MultiProbe(const QString &name,
                       const QString &dns_name,
                       MultiProbeFactory *factory)
    : Probe(name),
      factory_(factory),
      dns_name_(dns_name),
      probes_(),
      fault_(OK) {
}

MultiProbe::~MultiProbe() {
}

Probe *MultiProbe::GetLongProbe() {
  if (probes_.empty()) {
    return NULL;
  }
  // QList doesn't have an rbegin() method :(
  return probes_.at(probes_.size() - 1);
}

QString MultiProbe::GetDisplayText() {
  if (fault_ != OK) {
    CHECK(fault_ == RESOLVE_FAILED);
    return tr("The server %1 does not resolve in DNS",
              "%1 is a DNS hostname.").arg(dns_name_);
  };

  // If the long probe had an error, use that.
  // Otherwise, use any error you find.
  // Lastly, use the long probe in the case where all probes succeeded.
  Probe *long_probe = GetLongProbe();
  if (long_probe == NULL) {
    return tr("Test not yet run.");
  }
  int long_result = long_probe->GetFaultId();
  if (long_result != 0) {
    return long_probe->GetDisplayText();
  }
  for (StorageType::iterator i = probes_.begin();
       i != probes_.end(); ++i) {
    if ((*i)->GetFaultId() != 0) {
      return (*i)->GetDisplayText();
    }
  }
  return long_probe->GetDisplayText();
}

int MultiProbe::GetFaultId() {
  if (fault_ != OK) {
    return fault_;
  }

  if (probes_.empty()) {
    return 0;
  }

  // Report OK only if all candidates reported ok.
  // Prefer the fault from the long probe in the case where more
  // than one fault is found.
  ResultStatus rs = GetLongProbe()->result_status();
  if (rs != PROBE_OK) {
    return GetLongProbe()->GetFaultId();
  }

  for (StorageType::iterator i = probes_.begin(); i != probes_.end(); ++i) {
    ResultStatus rs = (*i)->result_status();
    if (rs != PROBE_OK) {
      return (*i)->GetFaultId();
    }
  }

  return GetLongProbe()->GetFaultId();
}

QString MultiProbe::GetProbeId() {
  // Return the probe id of any child.  They should be the same.
  if (probes_.empty()) {
    // This string literal should not be translated.
    return "unknown";
  }
  return GetLongProbe()->GetProbeId();
}

QString MultiProbe::DebugText() {
  // Concatenate our sub probe debug reports.
  QString rval;
  rval += Probe::DebugText();
  if (fault_ != 0) {
    // resolve failed.  use base implementation
    return Probe::DebugText();
  }
  for (StorageType::iterator i = probes_.begin(); i != probes_.end(); ++i) {
    rval += (*i)->DebugText();
  }
  return rval;
}

void MultiProbe::GetProgress(int *progress_made, int *total_estimated) {
  // Sum up the progress we've made across all sub probes.
  if (probes_.empty()) {
    // We haven't run yet:
    *progress_made = 0;
    *total_estimated = 1;
  }
  *progress_made = 0;
  *total_estimated = 0;
  for (StorageType::iterator i = probes_.begin();
       i != probes_.end(); ++i) {
    int probe_progress_made = 0;
    int probe_total_estimated = 0;
    (*i)->GetProgress(&probe_progress_made, &probe_total_estimated);
    *progress_made += probe_progress_made;
    *total_estimated += probe_total_estimated;
  }
  return;
}

void MultiProbe::StartProbe() {
  // Start the DNS resolution.
  ClearLog();
  StartLogDetails();
  QHostInfo::lookupHost(dns_name_, this,
                        SLOT(OnDnsResolutionComplete(QHostInfo)));
}

void MultiProbe::OnDnsResolutionComplete(const QHostInfo &hostinfo) {
  QList<QHostAddress> addresses = hostinfo.addresses();
  bool resolve_fault = false;
  if (hostinfo.error() != QHostInfo::NoError) {
    resolve_fault = true;
    LogDetails("Resolve", false, "Name " + dns_name_
               + " does not resolve in DNS: " + hostinfo.errorString());
  } else if (addresses.size() == 0) {
    // I'm not even sure if this is possible.  But keep the code just in case.
    resolve_fault = true;
    LogDetails("Resolve", false, "Name " + dns_name_
               + " resolves to no addresses.");
  }
  if (resolve_fault) {
    fault_ = RESOLVE_FAILED;
    emit ProbeComplete(PROBE_FAILED);
    return;
  }
  // Create the probes in random order.  Start the first one.
  std::random_shuffle(addresses.begin(), addresses.end());
  QString resolves_to = dns_name_ + ":<ul>";
  for (int i = 0; i < addresses.size(); ++i) {
    bool is_last = i == addresses.size() - 1;
    const QHostAddress &address = addresses.at(i);
    resolves_to += "<li>" + address.toString();
    // We're going to run the probes serially, with the
    // long probe run last:
    Probe *p = factory_->CreateProbe(address, is_last);
    CHECK(connect(p, SIGNAL(ProgressMade(int, int)),
                  this, SLOT(OnProgress(int, int))));
    CHECK(connect(p, SIGNAL(ProbeComplete(
                                ResultStatus)),
                  this, SLOT(OnProbeComplete(
                                 ResultStatus))));
    probes_.push_back(p);
  }
  next_probe_ = probes_.begin();
  resolves_to += "</ul>";
  LogDetails("Resolve", true, resolves_to);
  fault_ = OK;
  StartNextProbe();
}


void MultiProbe::OnProgress(int, int) {
  // One sub probe emitted progress.  Emit aggregate progress:
  int completed = 0;
  int estimated = 0;
  GetProgress(&completed, &estimated);
  emit ProgressMade(completed, estimated);
}

void MultiProbe::OnProbeComplete(ResultStatus) {
  // a sub probe has completed.  Update overall status.
  RunWhenEventQueueEmpties(this, SLOT(CheckForComplete()));
}

void MultiProbe::CheckForComplete() {
  // Either emit the completed signal, or start the next probe.
  bool done = true;
  for (StorageType::iterator i = probes_.begin(); i != probes_.end(); ++i) {
    ExecuteStatus es = (*i)->execute_status();
    if (es != FINISHED) {
      done = false;
      break;
    }
  }
  if (done) {
    int code = GetFaultId();
    emit ProbeComplete(code == 0 ? PROBE_OK : PROBE_FAILED);
  } else {
    StartNextProbe();
  }
}

void MultiProbe::StartNextProbe() {
  CHECK(next_probe_ != probes_.end());
  Probe *p = *next_probe_++;
  p->Start();
}

}  // namespace popdiag
